/***************************************************************************
 *   This is a part of the NextCAD Web APP.                                *
 *   Copyright (C) AMCAX, All rights reserved.                             *
 ***************************************************************************/

import {
  CENTEREVENTS,
  CSPoint,
  EventsRes,
  pickedFaceData,
  pickedVertexData,
  POINT_TYPE,
} from '@amcax/base';
import { eventCenterManager } from '@app-base/common/events/EventCenterManager';
import { PickInputRJSFFormat } from '@app-cad/common/components/command-form/constants';
import { PickableCommandForm } from '@app-cad/common/components/command-form/pickable';
import { MouseActionInput } from '@app-cad/common/components/pick-input/mouse-action-input';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { useTools } from '@app-cad/common/hooks/useTools';
import { ctx2 } from '@app-cad/module';
import { CurvedBaseCommand, CurvedCmdDialogParams } from '@app-cad/types';
import { RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
import { useAsyncEffect, useMemoizedFn } from 'ahooks';
import i18n from 'i18next';
import { omitBy } from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { Vector3, Vector3Tuple } from 'three-legacy';

type FormData = {
  surface?: pickedFaceData;
  point?: pickedVertexData | Vector3Tuple;
  dir: 'u' | 'v';
  pointMode: 'pick' | 'onFace';
};

type Params = {
  surface: string;
  point: string | Vector3Tuple;
  dir: 'u' | 'v';
};

const AxesVertexPicker = (props: WidgetProps<any, RJSFSchema, any>) => {
  return (
    <MouseActionInput
      onActiveFocus={() => props.onFocus?.(props.id, null)}
      onActiveBlur={() => props.onBlur?.(props.id, null)}
      placeholder={props.placeholder}
    >
      {props.value == null ? (
        ''
      ) : (
        <>
          <div>x: {props.value[0]}</div>
          <div>y: {props.value[1]}</div>
          <div>z: {props.value[2]}</div>
        </>
      )}
    </MouseActionInput>
  );
};

function CommandCurveIso({ title, onClose, props }: CurvedCmdDialogParams) {
  const initData = {
    dir: 'u',
    pointMode: 'pick',
  } as FormData;

  const [formData, setFormData] = useState<FormData>(initData);

  const surfaceRef = useRef<pickedFaceData>();

  useEffect(() => {
    surfaceRef.current = formData.surface;
  }, [formData]);

  const { picker } = useTools();

  useAsyncEffect(async () => {
    if (props == null) {
      setFormData(initData);
      return;
    }
    const params: Params = props.params;
    const { surface, point, dir } = params;

    const pointMode = typeof point === 'string' ? 'pick' : 'onFace';

    await initEditDependency(
      [surface, point].filter((v) => typeof v === 'string') as string[],
    );

    setFormData({
      surface: picker.generatePickObjByPickID(surface) as pickedFaceData,
      point:
        pointMode === 'onFace'
          ? (point as Vector3Tuple)
          : (picker.generatePickObjByPickID(
              point as string,
            ) as pickedVertexData),
      pointMode,
      dir,
    });
  }, []);

  const isFormDataValid = () => {
    const { surface, point } = formData;
    return surface != null && point != null;
  };

  const checkAndFinishDraw = async () => {
    if (!isFormDataValid()) {
      return;
    }

    await execute(false);

    onClose();
  };

  const preview = async () => {
    if (!isFormDataValid()) {
      clear();
      return;
    }

    await execute(true);
  };

  useAsyncEffect(preview, [formData]);

  const { initEditDependency, initingEdit, clear, confirmCommand, isLoading } =
    useCommand('cmd-curve-iso', {
      commandName: 'CreateCurve',
      editDxid: props?.shape,
      onClose,
      finishDraw: checkAndFinishDraw,
    });

  const execute = (preview: boolean) => {
    const { surface, point, dir } = formData;
    return confirmCommand(
      {
        type: 'Iso',
        a: {
          surface: surface.meshId,
          point:
            (point as pickedVertexData)?.vertexId ?? (point as Vector3Tuple),
          dir,
        },
      },
      preview,
    );
  };

  const schema: RJSFSchema = {
    type: 'object',
    properties: {
      surface: {
        type: 'string',
        format: PickInputRJSFFormat.PickSurface,
      },
      pointMode: {
        type: 'string',
        oneOf: [
          {
            type: 'string',
            title: '选取点',
            const: 'pick',
          },
          {
            type: 'string',
            title: '面上的点',
            const: 'onFace',
          },
        ],
        default: 'pick',
      },
      point: {
        type: 'string',
        format: PickInputRJSFFormat.PickVertex,
      },
      dir: {
        title: '方向',
        type: 'string',
        enum: ['u', 'v'],
        default: 'u',
      },
    },
  };

  const assistPointRef = useRef<CSPoint>();

  const registerSurfacePointPick = useMemoizedFn(
    (ev: EventsRes<PointerEvent>) => {
      if (surfaceRef.current != null && ev.button.onLeft) {
        const rayCaster = ctx2.viewer.sceneSetup.createRaycaster(
          ev.mouse.x,
          ev.mouse.y,
        );

        const res = rayCaster.intersectObject(
          surfaceRef.current.assisObj,
          true,
        );

        const firstPoint = res.sort((a, b) => a.distance - b.distance)[0]
          ?.point;

        if (firstPoint != null) {
          setFormData({
            ...formData,
            point: [firstPoint.x, firstPoint.y, firstPoint.z],
          });
        }
      }
    },
  );

  useEffect(() => {
    if (formData.pointMode === 'pick' || formData.point == null) {
      assistPointRef.current?.destroy();
      assistPointRef.current = null;
      ctx2.viewer.requestRender();

      return;
    }

    const point = formData.point as Vector3Tuple;

    assistPointRef.current?.destroy();
    assistPointRef.current = new CSPoint({
      vertex: new Vector3(...point),
      pointType: POINT_TYPE.ORIGIN,
    });

    assistPointRef.current.addTo(ctx2.cadScene.assisGroup);
    ctx2.viewer.requestRender();
  }, [formData.point]);

  useEffect(() => {
    return () => {
      eventCenterManager.activeEventCenter.off(
        CENTEREVENTS.POINTERDOWN,
        registerSurfacePointPick,
      );
      assistPointRef.current?.destroy();
    };
  }, []);

  return (
    <PickableCommandForm
      disabled={initingEdit}
      onClose={onClose}
      title={title}
      submitLoading={isLoading}
      schema={schema}
      formData={formData}
      onChange={(v) => {
        if (
          formData.pointMode === 'onFace' &&
          (formData.pointMode !== v.pointMode ||
            formData.surface?.meshId !== v.surface?.meshId)
        ) {
          setFormData({ ...v, point: null });
          return;
        }
        setFormData(v);
      }}
      extraUiSchema={{
        surface: {
          'ui:autofocus': true,
          'ui:placeholder': '请输入曲面',
        },
        point: omitBy(
          {
            'ui:placeholder': '请输入曲面上的点',
            'ui:widget':
              formData.pointMode === 'onFace' ? AxesVertexPicker : null,
          } as UiSchema,
          (v) => v == null,
        ),
      }}
      submitText='完成'
      onSubmit={checkAndFinishDraw}
      noValidate
      onFocus={(id) => {
        if (id.replace('root_', '') === 'point') {
          eventCenterManager.activeEventCenter.on(
            CENTEREVENTS.POINTERDOWN,
            registerSurfacePointPick,
          );
        }
      }}
      onBlur={(id) => {
        if (id.replace('root_', '') === 'point') {
          eventCenterManager.activeEventCenter.off(
            CENTEREVENTS.POINTERDOWN,
            registerSurfacePointPick,
          );
        }
      }}
      isFormDataValid={isFormDataValid()}
    />
  );
}

export const cmdCurveIso: CurvedBaseCommand = {
  id: 'CommandCurveIso',
  label: 'command.curve-iso',
  icon: 'commands/curved/iso',
  create: (label, onClose, props) => (
    <CommandCurveIso
      title={i18n.t(label)}
      onClose={onClose}
      props={props}
    />
  ),
};
